package algoritmo2;

import algoritmo.*;
import java.util.Random;

import algoritmo2.cruces.Cruce;
import algoritmo2.mutacion.Mutacion;

import java.util.List;
import utils.PEConstantes;

public class AGenetico {

    protected Individuo[] pob; // poblacion
    protected Individuo[] nuevaPob;// nueva poblacion
    protected int tam_pob;// tamanio de la poblacion
    protected int num_max_gen;// numero maximo de generaciones
    protected Individuo elMejor;
    protected int pos_mejor;// posicion del mejor cromosoma
    protected double prob_cruce;// probabilidad de cruce
    protected double prob_mut;// probabilidad de mutacion
    protected int numGeneracion; // numero de generaci�nes que llevamos hasta
    // el momento
    protected int idSeleccion;
    protected Mutacion mutacion;
    protected Cruce cruce;
    protected int hmax;
    protected boolean completa;
    protected List<String> cjtoFun;
    protected List<String> cjtoTer;
    protected double rate;

    public AGenetico(int tam_pob, int num_max_gen, double prob_cruce,
            double prob_mut, int idSeleccion, Mutacion idMutacion, Cruce idCruce, int hmax, boolean completa,
            List<String> cjtoFun, List<String> cjtoTer, double rate) {
        this.tam_pob = tam_pob;
        this.num_max_gen = num_max_gen;
        this.prob_cruce = prob_cruce;
        this.prob_mut = prob_mut;
        this.idSeleccion = idSeleccion;
        this.mutacion = idMutacion;
        this.cruce = idCruce;
        this.hmax = hmax;
        this.completa = completa;
        this.cjtoFun = cjtoFun;
        this.cjtoTer = cjtoTer;
        this.rate = rate;
    }

    public void incrementarGeneracion() {
        numGeneracion++;
    }

    public boolean terminado() {
        return numGeneracion >= num_max_gen;
    }

    public int getNumGeneracion() {
        return numGeneracion;

    }

    public Individuo getElMejor() {
        return elMejor;
    }

    public Individuo[] getPob() {
        return pob;
    }

    public void reproduccion() {
        int[] sel_cruce = new int[tam_pob];
        int num_sel_cruce = 0;
        Random random = new Random();
        double prob;
        for (int i = 0; i < tam_pob; i++) {
            prob = (double) (((double) random.nextInt(100)) / 100);

            if (prob < prob_cruce) {
                sel_cruce[num_sel_cruce] = i;
                num_sel_cruce++;
            }
        }

        if (num_sel_cruce % 2 == 1) {
            num_sel_cruce--;
        }

        Random randomPos = new Random();

        for (int i = 0; i < num_sel_cruce; i = i + 2) {
            // Creamos dos hijos
            Individuo hijo1 = new Individuo(hmax, completa, cjtoFun, cjtoTer);
            Individuo hijo2 = new Individuo(hmax, completa, cjtoFun, cjtoTer);

            cruce.cruce(pob[sel_cruce[i]], pob[sel_cruce[i + 1]], hijo1, hijo2);
            hijo1.setAptitud(hijo1.evalua(rate));
            hijo2.setAptitud(hijo2.evalua(rate));

            // Aqu� tenemos un par de descendientes. Vamos a sustituir a sus
            // progenitores
            // (Opcional) Ojo, tenemos que respetar la �lite si hay elitismo
            // if ((hijo1.getAptitud() < pob[sel_cruce[i]].evalua())
            // || (!pob[sel_cruce[i]].isElite())) {

            pob[sel_cruce[i]] = hijo1;
            
            // }

            // if ((hijo2.getAptitud() < pob[sel_cruce[i + 1]].evalua())
            // || (!pob[sel_cruce[i + 1]].isElite())) {

            pob[sel_cruce[i + 1]] = hijo2;
            // }
        }
    }

    /**
     * Este m�todo realiza la mutaci�n para toda la poblaci�n
     */
    public void mutacion() {

        // variable que indica si el individuo ha mutado
        boolean mutado;
        double prob;
        for (int i = 0; i < tam_pob; i++) {
            mutado = false;
            // for (int j = 0; j < pob[i].getLongitudCromosoma(); j++) {
            prob = Math.random();
            // (Opcional): A�ado la posibilidad del elitismo
            if ((prob < prob_mut)) { // && (!pob[i].isElite())) {
                mutado = true;
                // Mutamos seg�n el tipo de nuestro objeto (polimorfismo)

                mutacion.mutacion(pob[i]);
            }
            if (mutado) {
                pob[i].setAptitud(pob[i].evalua(rate));
            }
            // }
        }
    }

    public void seleccion(boolean elitismo, int porcentajeElitismo) {
        switch (idSeleccion) {
            case PEConstantes.SELECCION_RANKING:
                seleccionPorRanking();
                break;
            case PEConstantes.SELECCION_RULETA:
                seleccionRuleta(elitismo, porcentajeElitismo);
                break;
            case PEConstantes.SELECCION_TORNEO:
                seleccionPorTorneo();
                break;
            default:
                System.out.println("ERROR: Tipo de selecci�n no encontrada");
        }
    }

    private void seleccionPorRanking() {
        ordenarPoblacion();
        Individuo[] nuevaPob = new Individuo[tam_pob];
        nuevaPob[0] = pob[0].Clone();
        nuevaPob[1] = pob[0].Clone();
        int numeroPadres = 2;
        double[] segmentosFitness = rankPopulation();
        double segmentoEntero = segmentosFitness[segmentosFitness.length - 1];
        while (numeroPadres < nuevaPob.length) {
            double x = (double) (Math.random() * segmentoEntero);
            if (x <= segmentosFitness[0]) {
                /* First Idividual was Selected */
                nuevaPob[numeroPadres] = pob[0].Clone();
                numeroPadres++;
            } else {
                for (int i = 1; i < nuevaPob.length; i++) {
                    if (x > segmentosFitness[i - 1] && x <= segmentosFitness[i]) {
                        /** * i'th Idividual was Selected*/
                        nuevaPob[numeroPadres] = pob[i].Clone();
                        numeroPadres++;
                    }
                }
            }
        }
        pob = nuevaPob;
    }
    // ************* Elitismo *************//

    public Individuo[] separarElite(int tamanioElite) {
        // Si el usuario no quiere elitismo, dejamos todo tal cual
        if (tamanioElite == 0) {
            
            return null;
        } else {
            Individuo[] elite = new Individuo[tamanioElite];
            // Ordenamos la poblaci�n por aptitudes
            ordenarPoblacion();
            // Cogemos los tamanioElite mejores
            for (int i = 0; i < tamanioElite; i++) {
                elite[i] = pob[i].Clone();
            }
            return elite;
        }
    }

    private void ordenarPoblacion() {
        // Ordenamos la poblaci�n seg�n su aptitud usando mergesort
        mergesort(pob);
    }

    private void mergesort(Individuo[] poblacion) {
        int h = (int) Math.floor(poblacion.length / 2);
        int m = poblacion.length - h;

        // Creamos dos arrays auxiliares
        Individuo[] U = new Individuo[h];
        Individuo[] V = new Individuo[m];

        if (poblacion.length > 1) {
            // Caso recursivo
            for (int i = 0; i < h; i++) {
                U[i] = poblacion[i];
            }

            for (int j = 0; j < m; j++) {
                V[j] = poblacion[h + j];
            }
            mergesort(U);
            mergesort(V);
            combinar(U, V, poblacion);
        }
    }

    private void combinar(Individuo[] u, Individuo[] v, Individuo[] poblacion) {
        int i = 0;
        int j = 0;
        int k = 0;

        while (i < u.length && j < v.length) {
            if (u[i].getAptitud() > v[j].getAptitud()) {
                poblacion[k] = u[i];
                i++;
            } else {
                poblacion[k] = v[j];
                j++;
            }
            k++;
        }
        if (i >= u.length) {
            for (int l = k; l < u.length + v.length; l++) {
                poblacion[l] = v[j + l - k];
            }
        } else {
            for (int l = k; l < u.length + v.length; l++) {
                try {
                    poblacion[l] = u[i + l - k];
                } catch (Exception e) {
                    System.out.println("i + l - k = " + (i + l - k) + " l = " + l + " long. pob = " + pob.length + " long u = " + u.length);
                }
            }
        }
    }

    private double[] rankPopulation() {
        // Supongo un beta de 1.5
        double[] segmentosFitness = new double[tam_pob];
        for (int i = 0; i < segmentosFitness.length; i++) {
            double probOfIth = (double) i / tam_pob;
            probOfIth = probOfIth * 2 * (1.5 - 1);
            probOfIth = 1.5 - probOfIth;
            probOfIth = (double) probOfIth * ((double) 1 / tam_pob);
            if (i != 0) {
                segmentosFitness[i] = segmentosFitness[i - 1] + probOfIth;
            } else {
                segmentosFitness[i] = probOfIth;
            }
        }
        return segmentosFitness;
    }

    private void seleccionPorTorneo() {
        // Declaramos los �ndices de los individuos que vamos a seleccionar
        int ind1, ind2, ind3;

        // Declaramos el individuo o individuos seleccionados
        Individuo[] seleccionados = new Individuo[tam_pob];

        // Declaramos el �ndice mejor
        int indiceMejor;

        // Generamos un n�mero al azar
        Random semilla = new Random();

        for (int i = 0; i < tam_pob; i++) {
            ind1 = semilla.nextInt(tam_pob);
            ind2 = semilla.nextInt(tam_pob);
            ind3 = semilla.nextInt(tam_pob);

            // Vemos cu�l es el mejor de los tres
            if (pob[ind1].getAptitud() > pob[ind2].getAptitud() && pob[ind1].getAptitud() > pob[ind3].getAptitud()) {
                indiceMejor = ind1;
            } else if (pob[ind2].getAptitud() > pob[ind1].getAptitud() && pob[ind2].getAptitud() > pob[ind3].getAptitud()) {
                indiceMejor = ind2;
            } else {
                indiceMejor = ind3;
            }
            seleccionados[i] = pob[indiceMejor].Clone();
        }
        // En este punto tenemos una nueva poblaci�n en el array de
        // seleccionados
        pob = seleccionados;
    }

    // Copiado de la pr�ctica anterior
    public void seleccionRuleta(boolean elitismo, int porcentajeElitismo) {
        int[] sel_super = new int[tam_pob];
        double prob;
        int pos_super;
        for (int i = 0; i < tam_pob; i++) {
            prob = Math.random();
            pos_super = 0;
            while (pos_super < tam_pob && prob > pob[pos_super].getPunt_acu()) {
                pos_super++;
            }

            if (pos_super >= tam_pob) {
                pos_super--;
            }

            sel_super[i] = pos_super;
        }

        // Creamos el array con los cromosomas elegidos

        Individuo[] elegidos = new Individuo[tam_pob];
        for (int i = 0; i < tam_pob; i++) {
            elegidos[i] = pob[sel_super[i]].Clone();
        }

        pob = elegidos;
        // double prob;
        // int pos_super;
        // int[] selSuper = new int[tam_pob];
        // Cromosoma[] pob_intermedia = new Cromosoma[tam_pob]; // Poblaci�n
        // // intermedia
        // // Diferenciamos si el usuario ha permitido elitismo o no
        // // El elitismo NO siempre mejora el resultado del algoritmo
        // if (!elitismo) {
        // nuevaPob = new Cromosoma[tam_pob];
        //
        // for (int i = 0; i < tam_pob; i++) {
        // prob = Math.random();
        // pos_super = 0;
        // while ((prob > pob[pos_super].getPunt_acum())
        // && (pos_super < tam_pob)) {
        // pos_super++;
        // }
        // selSuper[i] = pos_super;
        // }
        // // Se genera la poblaci�n intermedia
        // for (int i = 0; i < tam_pob; i++) {
        // pob_intermedia[i] = pob[selSuper[i]].Clone();
        // }
        // for (int i = 0; i < tam_pob; i++) {
        // pob[i] = pob_intermedia[i];
        // }
        // } else {
        // // Definimos el tama�o de la �lite. Como se coment� en clase,
        // // debe ser poco numerosa
        // int tam_elite = porcentajeElitismo * pob.length / 100;
        // nuevaPob = new Cromosoma[tam_pob];
        // // Definimos los seleccionados como �lite
        // int[] selElite = new int[tam_elite];
        // // int[] selSuper = new int[tam_pob];
        // // Inicializaci�n
        // for (int i = 0; i < tam_elite; i++) {
        // selElite[i] = 0;
        // }
        // for (int i = 0; i < tam_pob; i++) {
        // pob[i].setElite(false);
        // int j = 0;
        // while ((j < tam_elite)
        // && (pob[selElite[j]].getAptitud() <= pob[i]
        // .getAptitud())) {
        // j++;
        // }
        // if (j < tam_elite) {
        // selElite[j] = i;
        // }
        // for (int k = 0; k < tam_elite; k++) {
        // pob[selElite[k]].setElite(true);
        // }
        //
        // // Ahora se seleccionan individuos de acuerdo con su
        // // puntuaci�n
        // // acumulada
        // for (int l = 0; l < tam_pob; l++) {
        // if (pob[l].isElite()) {
        // selSuper[l] = l;
        // } else {
        //
        // prob = Math.random();
        // pos_super = 0;
        // while ((prob > pob[pos_super].getPunt_acum())
        // && (pos_super < tam_pob)) {
        // pos_super++;
        // }
        // if (pos_super < tam_pob) {
        // selSuper[l] = pos_super;
        // } else {
        // selSuper[l] = pos_super - 1;
        // }
        // }
        // }
        // }
        //
        // // Se genera la nueva poblaci�n intermedia
        // for (int i = 0; i < tam_pob; i++) {
        // // nuevaPob[i] = pob[selSuper[i]];
        // pob_intermedia[i] = pob[selSuper[i]].Clone();
        // }
        //
        // // Ahora copiamos la poblaci�n intermedia sobre la poblaci�n
        // for (int i = 0; i < tam_pob; i++) {
        // pob[i] = pob_intermedia[i].Clone();
        // }
        // }
    }

    public void inicializa() {

        pob = new Individuo[tam_pob];
        
        boolean noSalir = true;
        while (noSalir) {
            noSalir = false;
            for (int i = 0; i < tam_pob; i++) {
                pob[i] = new Individuo(hmax, completa, cjtoFun, cjtoTer);
                pob[i].inicializaCromosoma();
                // Solo para la parte 2 (los �ndices comienzan en 0)

                pob[i].inicializaCromosoma();

                pob[i].setAptitud(pob[i].evalua(rate));
                                
            }
        }

    }

    
    public void evaluarPoblacion(boolean escalabilidad, double presion) {

        double sumaAptitud = 0;
        double punt_acu = 0;
        double aptitud_mejor = Double.MIN_VALUE;
        double aptitudAuxiliar = 0;

        double aptitud_mayor = 0;
        double media;

        //adaptarMinimizacion();

        for (int i = 0; i < tam_pob; i++) {
            sumaAptitud += pob[i].getAptitud();
            if (pob[i].evalua(rate) > aptitud_mejor) {
                pos_mejor = i;
                aptitud_mejor = pob[i].evalua(rate);
            }
        }

        /*** Parte para escalabilidad */
        if (escalabilidad) {
            double suma = 0;
            for (int i = 0; i < tam_pob; i++) {
                suma += pob[i].getAptitud();
                if (pob[i].getAptitud() > aptitud_mayor) {
                    aptitud_mayor = pob[i].getAptitud();
                }
            }
            media = suma / tam_pob;
            double a = ((presion - 1) * media) / (aptitud_mayor - media);
            double b = (1 - a) * media;
            for (int i = 0; i < tam_pob; i++) {
                double valor = a * pob[i].getAptitud() + b;
                if(valor < 0)
                    valor = 0;
                pob[i].setAptitud(valor);
            }
        }
        /************* Acaba la parte para escalabililidad */
        // rellenamos las puntaciones acumuladas y relativas de toda la
        // poblacion de cromosomas
        for (int i = 0; i < tam_pob; i++) {
            pob[i].setPuntuacion(pob[i].getAptitud() / sumaAptitud);
            pob[i].setPunt_acu(pob[i].getPuntuacion() + punt_acu);
            punt_acu += pob[i].getPuntuacion();
        }

        // copiamos el mejor

        if (elMejor == null || aptitud_mejor > elMejor.evalua(1)) {

            Individuo cromosoma = (Individuo) pob[pos_mejor];
            elMejor = cromosoma.Clone();

            //System.out.println("Nuevo mejor " + elMejor.evalua());

        }
    }

    public void incluyeElite(Individuo[] elite) {
        // Si la �lite es null (no hay elitismo), no hacemos nada
        if (elite != null) {
            ordenarPoblacion();

            int j = 0;
            int k = pob.length - 1;
            // this.seleccionElite = new Cromosoma[this.tamPoblacion];

            // Cromosoma[] pob = new Cromosoma[this.tamPoblacion];

            while (j < elite.length) {

                if (elite[j].getAptitud() > pob[k].getAptitud()) {
                    pob[k] = elite[j];
                    k--;
                    j++;
                } else {
                    j++;
                }
            }
        }
    }
}
